﻿namespace Baddies.Nodes
{    
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Xml.Serialization;
    using Baddies.Utils;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.Graphics;

    /// <summary>
    /// The node class is a base class for all elements that require to
    /// be updated in the main loop. 
    /// A node can add nodes to itself and they will be updated and drawn 
    /// in turn, and their children, and so on.
    /// </summary>
    public class Node
    {
        #region Members
        /// <summary>
        /// A list of all the node's children, indexed by arbitrary tags.
        /// </summary>
        private SharedResourceDictionary<int, Node> children;

        /// <summary>
        /// Position of the node in absolute coords.
        /// The Z component indicates the draw order 
        /// with respect to the rest of the children of the node.
        /// </summary>
        private Vector3 absoluteP;

        /// <summary>
        /// Position of the node with respect to it's parent.
        /// </summary>
        private Vector3 position;

        /// <summary>
        /// Indicates whether the node must be drawn or not.
        /// </summary>
        private bool visible;

        /// <summary>
        /// Indicates whether the node's position must be relative to the parent
        /// or the camera.
        /// False: Drawing relative to the parent's position.
        /// True: Drawing relative to the camera.
        /// </summary>
        private bool parentRelativePos;

        /// <summary>
        /// Name of the node. Debugging uses.
        /// </summary>
        private string name;

        /// <summary>
        /// Parent node of this node.
        /// </summary>        
        private Node parent;

        /// <summary>
        /// Indicates whether the node has to be culled from the draw pass.
        /// </summary>
        private bool mustCull;

        /// <summary>
        /// Gets or sets the name value.
        /// </summary>
        /// <value>Name of the node.</value>
        public string Name
        {
            get { return this.name; }
            set { this.name = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the node depends
        /// on the position of the parent.
        /// </summary>
        /// <value>Bool indicating if the position of the parent matters.</value>
        public bool ParentRelativePos
        {
            get { return this.parentRelativePos; }
            set { this.parentRelativePos = value; }
        }

        /// <summary>
        /// Gets the children of the node.
        /// </summary>
        /// <value>The children.</value>
        public Dictionary<int, Node> Children
        {
            get { return this.children; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the node is visible.
        /// </summary>
        /// <value>The visibility of the node.</value>
        public bool Visible
        {
            get { return this.visible; }
            set { this.visible = value; }
        }

        /// <summary>
        /// Gets the absolute position which is used for drawing.
        /// </summary>
        /// <value>Draw position.</value>
        public Vector3 DrawPosition
        {
            get { return this.absoluteP; }
        }

        /// <summary>
        /// Gets or sets the relative position of the node.
        /// </summary>
        /// <value>The position of the node.</value>
        public Vector3 Position
        {
            get 
            { 
                return this.position; 
            }

            set
            {
                this.position = value;
            }
        }

        /// <summary>
        /// Gets or sets the parent of the node.
        /// </summary>
        /// <value>The parent of the node.</value>
        [ContentSerializerAttribute(SharedResource = true)]
        public Node Parent
        {
            get 
            {
                return this.parent;
            }

            set 
            {
                this.parent = value;
            }
        }

        /// <summary>
        /// Gets whether the node contains the specified child node.
        /// </summary>
        /// <param name="child">Child to check.</param>
        /// <returns>True if it contains it, false otherwise.</returns>
        public bool Contains(Node child)
        {
            return this.children.ContainsValue(child);
        }

        /// <summary>
        /// Gets whether the node contains the specified child tag.
        /// </summary>
        /// <param name="child">Child to check.</param>
        /// <returns>True if it contains it, false otherwise.</returns>
        public bool Contains(int child)
        {
            return this.children.ContainsKey(child);
        }

        #endregion // Members

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the Node class.
        /// </summary>
        public Node()
        {
            this.children = new SharedResourceDictionary<int, Node>();
            this.position = new Vector3();
            this.absoluteP = new Vector3();
            this.visible = true;
            this.parentRelativePos = true;
            this.name = "Node";
            this.parent = null;
            this.mustCull = false;
        }

        #endregion // Constructors

        #region LogicFlow
        /// <summary>
        /// Update function to be overriden in base classes.
        /// </summary>
        public virtual void Update() 
        { 
        }

        /// <summary>
        /// Draw function to be overrriden in base classes.
        /// </summary>
        /// <param name="spritebatch">Spritebatch to draw on.</param>
        public virtual void Draw(SpriteBatch spritebatch)
        {
        }

        /// <summary>
        /// Initialization called after adding the node to a parent.
        /// To be overrriden in base classes.
        /// </summary>
        public virtual void Enter()
        {
        }

        /// <summary>
        /// Called before removing from parent.
        /// To be overrriden in base classes.
        /// </summary>
        public virtual void End()
        {
        }

        /// <summary>
        /// Indicates whether the node must be culled from drawing.
        /// To be overrriden in base classes.
        /// </summary>
        /// <param name="camera">Camera to cull against.</param>
        /// <returns>True if we have tu cull it.</returns>
        public virtual bool Cull(Camera camera)
        {
            return false;
        }

        /// <summary>
        /// Updates the node and then all the children.
        /// </summary>
        public virtual void NodeUpdate()
        {
            this.Update();

             foreach (KeyValuePair<int, Node> child in this.children)
            {
                child.Value.NodeUpdate();
            }
        }

        /// <summary>
        /// Draws the Node and then the children..
        /// </summary>
        /// <param name="spritebatch">Spritebatch to draw on.</param>
        public virtual void NodeDraw(SpriteBatch spritebatch)
        {
            if (!this.visible)
            {
                return;
            }

            if (this.mustCull == false)
            {
                // Calcualte draw position here.
                if (this.parentRelativePos)
                {
                    this.absoluteP.X = this.position.X + this.parent.absoluteP.X;
                    this.absoluteP.Y = this.position.Y + this.parent.absoluteP.Y;
                    this.absoluteP.Z = this.position.Z + this.parent.absoluteP.Z;
                }
                else
                {
                    this.absoluteP = this.position;
                }

                this.Draw(spritebatch);
            }

            // Z-ordered draw
            var items = from k in this.children.Keys 
                        orderby this.children[k].Position.Z ascending 
                        select this.children[k];

            foreach (Node child in items)
            {
                child.NodeDraw(spritebatch);
            }

            this.mustCull = false;
        }

        /// <summary>
        /// Checks if the node must be culled and then checks the children.
        /// Even if a node is culled its children might not be, as the children
        /// do not need to be strictly inside the parent space.
        /// </summary>
        /// <param name="camera">Camera to cull against.</param>
        public virtual void NodeCull(Camera camera)
        {
            this.mustCull = this.Cull(camera);
            foreach (KeyValuePair<int, Node> child in this.children)
            {
                child.Value.NodeCull(camera);
            }
        }

        /// <summary>
        /// Calls the "End" function on all the children and then the node.
        /// </summary>
        public virtual void NodeEnd()
        {            
            foreach (KeyValuePair<int, Node> child in this.children)
            {
                child.Value.NodeEnd();
            }

            this.End();
        }

        #endregion // LogicFlow

        #region TreeModification

        /// <summary>
        /// Adds a new child to the Node.
        /// </summary>
        /// <param name="tag">Int identifier to associate with the child.</param>
        /// <param name="child">New node to add.</param>
        public void Add(int tag, Node child)
        {            
            // Check tag is not already in use            
            bool existsKey = this.children.ContainsKey(tag);
            Debug.Assert(!existsKey, "Child with the key" + tag + "already exists in this node.");

            // Check the child is valid
            Debug.Assert(child != null, "The child to add is NULL! D: ");

            // Add child
            this.children.Add(tag, child);

            // Set parent
            child.Parent = this;

            // Call Enter
            child.Enter();
        }

        /// <summary>
        /// Gets the child node associated with the especified tag.
        /// Returns Null if the tag is not in the dictionary of children.
        /// </summary>
        /// <param name="tag">Reference for the Child we want.</param>
        /// <returns>Node indexed by tag.</returns>
        public Node Get(int tag)
        {            
            Node returnChild = null;

            bool found = this.children.TryGetValue(tag, out returnChild);

            // We don't know what the "default" value for a Node is, so 
            // if the call failed, we return null regardless.
            if (!found)
            {
                returnChild = null;
            }

            return returnChild;
        }

        /// <summary>
        /// Removes the required Node from this parent.
        /// </summary>
        /// <param name="tag">Reference to the node we want to remove.</param>
        public void Remove(int tag)
        {            
            // Check tag is not already in use            
            bool existsKey = this.children.ContainsKey(tag);
            Debug.Assert(existsKey, "Child with the key" + tag + "does not exist in this node.");

            // Call end in all the children.
            this.children[tag].NodeEnd();

            // Remove child
            this.children.Remove(tag);
        }

        /// <summary>
        /// Removes the required Node from this parent, calling 
        /// the child's Exit() on the process.
        /// <remarks>Faster to call the Remove(int) version.</remarks>
        /// </summary>
        /// <param name="child">Node we want to remove.</param>
        public void Remove(Node child)
        {            
            // Check the node is not null
            Debug.Assert(child != null, "Node to remove is null!");

            // Check such a node exists
            bool existsValue = this.children.ContainsValue(child);
            Debug.Assert(existsValue, "Child " + child + "does not exist in this node.");

            // Remove child
            int tag = 0;
            bool found = false;
            foreach (KeyValuePair<int, Node> pair in this.children)
            {
                if (pair.Value.Equals(child))
                {
                    tag = pair.Key;
                    found = true;
                }
            }

            Debug.Assert(found, "Getting the key from the node provided dind't work.");
            this.children.Remove(tag);
        }

        /// <summary>
        /// Sets the X position of the node, with no safety checks.
        /// Use for debugging and special cases.
        /// </summary>
        /// <param name="x">New x value.</param>
        public void DirectSetX(float x)
        {
            this.position.X = x;
        }

        /// <summary>
        /// Sets the Y position of the node, with no safety checks.
        /// Use for debugging and special cases.
        /// </summary>
        /// <param name="y">New y value.</param>
        public void DirectSetY(float y)
        {
            this.position.Y = y;
        }

        /// <summary>
        /// Sets the Z position of the node, with no safety checks.
        /// Use for debugging and special cases.
        /// </summary>
        /// <param name="z">New z value.</param>
        public void DirectSetZ(float z)
        {
            this.position.Z = z;
        }

        /// <summary>
        /// Offsets the position of the node.
        /// </summary>
        /// <param name="x">X offset value.</param>
        /// <param name="y">Y offset value.</param>
        /// <param name="z">Z offset value.</param>
        public void OffsetPosition(float x, float y, float z)
        {
            this.position.X += x;
            this.position.Y += y;
            this.position.Z += z;
            this.UpdateChildrenPosition(x, y, z);
        }

        /// <summary>
        /// Offsets the actual absolute pos.
        /// Does not update the children, so to be used internaly only.
        /// </summary>
        /// <param name="x">X offset value.</param>
        /// <param name="y">Y offset value.</param>
        /// <param name="z">Z offset value.</param>
        private void UnsafeOffsetPosition(float x, float y, float z)
        {
            this.position.X += x;
            this.position.Y += y;
            this.position.Z += z;
        }

        /// <summary>
        /// Called to update the children when the parent node moves.
        /// </summary>
        /// <param name="x">X offset value.</param>
        /// <param name="y">Y offset value.</param>
        /// <param name="z">Z offset value.</param>
        private void UpdateChildrenPosition(float x, float y, float z)
        {
            foreach (KeyValuePair<int, Node> pair in this.children)
            {
                if (pair.Value.ParentRelativePos)
                {
                    pair.Value.UnsafeOffsetPosition(x, y, z);
                    pair.Value.UpdateChildrenPosition(x, y, z);
                }
            }
        }

        #endregion // TreeModification

        #region Strings
        /// <summary>
        /// Gets the string that identifies the node.
        /// </summary>
        /// <returns>String with the name of the node.</returns>
        public override string ToString()
        {
            return this.name + ": " + this.position;
        }

        /// <summary>
        /// Prints the subtree which has this node as root 
        /// to a string and returns it.
        /// </summary>
        /// <returns>A formated string of the subtree.</returns>
        public string PrintSubtree()
        {
            string subtree = string.Empty;

            subtree = this.PrintSubtree(0);

            return subtree;
        }

        /// <summary>
        /// Generates an indented string with the specified initial spacing
        /// and one more of spacing on each level.
        /// </summary>
        /// <param name="spacing">Int indicating the indentation of the line.</param>
        /// <returns>A formated string of the subtree.</returns>
        public string PrintSubtree(int spacing)
        {
            // Ensure a positive spacing
            Debug.Assert(spacing >= 0, "Negative spacing for the subtree, really?");
            
            // Generate string for this node
            string subtree = string.Empty;

            for (int i = 0; i < spacing; i++)
            {
                subtree += "+";
            }

            subtree += spacing + ": ";
            subtree += this.ToString();
            subtree += System.Environment.NewLine;

            // Generate string for the child subtrees
            foreach (KeyValuePair<int, Node> pair in this.children)
            {
                subtree += pair.Value.PrintSubtree(spacing + 1);
            }

            return subtree;
        }

        #endregion // Strings
    }
}

